﻿using OpenCvSharp;
using System.Diagnostics;

namespace VisionProcess
{
    public abstract class ImageProcess
    {
        private static string templatePath01 = @"E:\Template\Template01";
        // Xray02模板目录
        private static string templatePath02 = @"E:\Template\Template02";

        // 查找板边缘使用的分隔阈值
        private static int findBoardThres = 200;
        /// <summary>
        /// 显示图像,调试的时候打开用来显示图像
        /// </summary>
        /// <param name="winName"></param>
        /// <param name="image"></param>
        /// <param name="showState"></param>
        private static void ImageShow(string winName, Mat image, bool showState = false)
        {
            if (showState && image != null)
            {
                int showImageScale = 1;
                if (image.Width > 1000)
                {
                    showImageScale = 2;
                }
                Size newSize = new Size(image.Width / showImageScale, image.Height / showImageScale);
                Mat showImage = new Mat();
                Cv2.Resize(image, showImage, newSize, interpolation: InterpolationFlags.Area);
                Cv2.NamedWindow(winName, WindowFlags.AutoSize);
                Cv2.ImShow(winName, showImage);
                Cv2.WaitKey(0);
            }
        }

        public static OperateResult<(Point[], Mat)> FindBoardEdge(Mat imageSrc)
        {
            OperateResult<(Point[], Mat)> result = new OperateResult<(Point[], Mat)>();
            try
            {
                ImageShow("Original", imageSrc);
                // 1. 求平均亮度的时候的Val0代表什么意思,这里先求出来最左侧区域是否是白色区域
                int meanVal = (int)imageSrc[new Rect(2, 62, 18, 415)].Mean().Val0;
                Console.WriteLine($"leftBlankMeanVal: {meanVal}");
                if (meanVal < 150)
                {
                    // 这里直接返回出错的结果,或者是用最边缘的地方作为边界
                }

                // 2. 选择使用的找板边的区域,因为只有一部分,所以这里使用的是靠上面的部分
                Mat image = imageSrc.Clone();
                Mat imageUsed = image[new Rect(0, 61, image.Width, 100)];
                ImageShow("ImageUsed", imageUsed);
                Cv2.GaussianBlur(imageUsed, imageUsed, new Size(0, 0), 2);
                ImageShow("BoardBlurred", imageUsed);
                Mat thresRes = new Mat();
                Cv2.Threshold(imageUsed, thresRes, findBoardThres, 255, ThresholdTypes.BinaryInv);
                ImageShow("BoardThres", thresRes);
                Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
                Mat opennedRes = new Mat();
                Cv2.MorphologyEx(thresRes, opennedRes, MorphTypes.Open, kernel, null, 3);
                ImageShow("BoardOpenned", opennedRes);
                int filterArea = 800;
                Point[][] contours;
                HierarchyIndex[] hierarchy;
                Cv2.FindContours(opennedRes, out contours, out hierarchy, RetrievalModes.List, ContourApproximationModes.ApproxSimple);
                var contoursList = contours.Select(c => new { Contour = c, Area = Cv2.ContourArea(c) });
                Console.WriteLine($"找到的视为物料的各个区域的面积为: {string.Join(", ", contoursList.Select(c => c.Area))}");
                var largeContours = contoursList.Where(c => c.Area > filterArea)
                    .Select(c => c.Contour);
                Console.WriteLine($"找到的符合要求的区域的个数为: {largeContours.Count()}");

                if (largeContours.Count() == 0)
                {
                    return result;
                }
                else
                {
                    var largestContour = largeContours.OrderByDescending(c => Cv2.ContourArea(c)).FirstOrDefault();
                    Cv2.DrawContours(imageUsed, new Point[][] { largestContour! }, -1, new Scalar(0, 255, 0), 2);
                    ImageShow("LargestContour", imageUsed);
                    RotatedRect box = Cv2.MinAreaRect(largestContour!);
                    double angle = box.Angle;
                    Console.WriteLine($"找到的物料区域的原始角度为: {angle},宽度为:{box.Size.Width},高度为{box.Size.Height}");

                    if (angle > 70)
                    {
                        angle = 90 - angle;
                        box = new RotatedRect(box.Center, new Size2f(box.Size.Height, box.Size.Width), (float)angle);
                    }
                    else if (angle < -70)
                    {
                        angle = angle + 90;
                        box = new RotatedRect(box.Center, new Size2f(box.Size.Height, box.Size.Width), (float)angle);
                    }

                    Point2f[] rectPoints = Cv2.BoxPoints(box);
                    Point2f[] rectPointsOrdered = GetRectPointsClockwise(rectPoints);
                    Point[] rectPointsInt = rectPointsOrdered.Select(p => new Point((int)p.X, (int)p.Y + 61)).ToArray();
                    Mat mask = new Mat(imageUsed.Size(), MatType.CV_8UC1, Scalar.All(0));
                    Cv2.DrawContours(mask, new[] { rectPointsInt }, -1, Scalar.All(255), -1);
                    ImageShow("BoardFinalFinded", mask);

                    Mat imageColor = new Mat();
                    Cv2.CvtColor(image, imageColor, ColorConversionCodes.GRAY2BGR);
                    Cv2.Line(imageColor, rectPointsInt[0], rectPointsInt[3], Scalar.Green, 3);
                    Cv2.Line(imageColor, rectPointsInt[3], rectPointsInt[2], Scalar.Green, 3);
                    Cv2.Circle(imageColor, rectPointsInt[3], 5, Scalar.Red, -1);
                    ImageShow("EdgeFinded", imageColor);
                }

            }
            catch (Exception ex)
            {

            }
            return result;
        }

        private static Point2f[] GetRectPointsClockwise(Point2f[] rectPoints)
        {
            // 根据矩形的四个顶点,获取其顺时针方向的四个顶点
            var rectPointsSorted = rectPoints.OrderBy(p => p.X + p.Y).ToArray();
            var leftTop = rectPointsSorted[0];
            var rightBottom = rectPointsSorted[^1]; // 使用C# 8.0的尾部索引语法

            Point2f rightTop, leftBottom;
            if (rectPointsSorted[1].X > rectPointsSorted[2].X)
            {
                rightTop = rectPointsSorted[1];
                leftBottom = rectPointsSorted[2];
            }
            else
            {
                rightTop = rectPointsSorted[2];
                leftBottom = rectPointsSorted[1];
            }

            Point2f[] pointRes = new Point2f[4];
            pointRes[0] = leftTop;
            pointRes[1] = rightTop;
            pointRes[2] = rightBottom;
            pointRes[3] = leftBottom;
            return pointRes;
        }

        // 加载靶标模板,最好是将模板传递过来.
        public static List<Mat> TemplateList01 = new List<Mat>();
        // 模板
        public static List<Mat> TemplateList02 = new List<Mat>();

        // 使用的匹配得分阈值
        public static double matchThres = 0.6;


        public static OperateResult LoadTemplate()
        {
            OperateResult result = new OperateResult();
            try
            {
                if (!Directory.Exists(templatePath01) || !Directory.Exists(templatePath02))
                {
                    string msgError = $"模板目录{templatePath01} 或者 {templatePath02} 目录不存在";
                    result.Message = msgError;
                }

                var files01 = Directory.GetFiles(templatePath01);
                foreach (var file in files01)
                {
                    Mat temp01 = Cv2.ImRead(file, ImreadModes.Grayscale);
                    TemplateList01.Add(temp01);
                }
                var files02 = Directory.GetFiles(templatePath02);

                foreach (var file in files02)
                {
                    Mat temp02 = Cv2.ImRead(file, ImreadModes.Grayscale);
                    TemplateList02.Add(temp02);
                }

                if (TemplateList01.Count > 0 && TemplateList02.Count > 0)
                {
                    result.State = true;
                }
                if (TemplateList01.Count == 0)
                {
                    result.Message = $"Xray01模板加载失败,没有任何模板,目录为{templatePath01}";
                }
                if (TemplateList02.Count == 0)
                {
                    result.Message = $"Xray02模板加载失败,没有任何模板,目录为{templatePath02}";
                }

            }
            catch (Exception e)
            {
                result.Message = e.Message;
            }

            return result;

        }

        public static Mat GetHistImage(Mat? ImageSrc)
        {
            // 先进行中值滤波
            Mat imageBlur = new Mat();
            Cv2.MedianBlur(ImageSrc!, imageBlur, 5);
            ImageShow("ImageMedia", imageBlur);
            Mat ImageEq = new Mat();
            Cv2.EqualizeHist(imageBlur, ImageEq);
            return ImageEq;
        }


        public static OperateResult<Tuple<double, Mat>> FindTargetInfo(Mat image, int xrayIndex)
        {
            //// 先将图像旋转矫正
            //Mat imageRoatated = RotateImageToRightPos(image);

            OperateResult<Tuple<double, Mat>> opRes = new OperateResult<Tuple<double, Mat>>();
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Mat imageColor = new Mat();
            Cv2.CvtColor(image, imageColor, ColorConversionCodes.GRAY2BGR);
            Mat ImageEq = GetHistImage(image);
            ImageShow("ImageEq", ImageEq);
            //Mat imageClache = new Mat();
            //var cla = Cv2.CreateCLAHE(clipLimit: 2.0, tileGridSize: new Size(8, 8));
            //cla.Apply(image, imageClache);
            //ImageShow("ImageClache", imageClache);

            List<Mat> templateList = new List<Mat>();
            if (xrayIndex == 1)
            {
                templateList = TemplateList01;
            }
            else
            {
                templateList = TemplateList02;
            }

            List<Tuple<double, Point, Size>> resList = new List<Tuple<double, Point, Size>>();

            int templateIndex = 1;
            foreach (Mat template in templateList)
            {
                Mat imageColorUsed = imageColor.Clone();
                Mat matchRes = new Mat();
                /*
                 * TM_SQDIFF: 平方差匹配法。此方法采用模板和图像间的平方差进行匹配，最好的匹配是最小值。这种方法在目标颜色和背景颜色差异较大时效果较好。

                    TM_SQDIFF_NORMED: 归一化平方差匹配法。与TM_SQDIFF类似，但是在匹配前对每个块进行了归一化处理。这种方法适用于大小相同的图像。

                    TM_CCORR: 相关匹配法。此方法使用模板和图像间的乘法操作。如果模板和图像块完全相同，则结果为最大值。这种方法适用于亮度变化不大的图像。

                    TM_CCORR_NORMED: 归一化相关匹配法。与TM_CCORR类似，但是在匹配前对每个块进行了归一化处理。

                    TM_CCOEFF: 相关系数匹配法。此方法使用模板和图像间的相关系数来进行匹配，即两者间的协方差除以它们的标准差。这种方法对亮度和对比度的变化具有鲁棒性。

                    TM_CCOEFF_NORMED: 归一化相关系数匹配法。与TM_CCOEFF类似，但是对结果进行了归一化处理，使得结果更加稳定。
                    
                   TM_SQDIFF 和 TM_SQDIFF_NORMED：当你需要查找颜色对比明显的模板时，使用这些方法。最匹配的点是最暗的点。

                    TM_CCORR 和 TM_CCORR_NORMED：当模板和图像的亮度水平非常相近时，这些方法效果很好。最匹配的点是最亮的点。

                    TM_CCOEFF 和 TM_CCOEFF_NORMED：这些方法对光照变化和模板与图像间的线性变化具有较高的鲁棒性。
                    它们尝试去除图像块的平均亮度，并通过计算系数的方式来找到最佳匹配。对于存在亮度和对比度变化的图像匹配问题，这两种方法通常是首选。
                    
                 */
                Cv2.MatchTemplate(ImageEq, template, matchRes, TemplateMatchModes.CCoeffNormed);
                double minVal, maxVal;
                Point minLoc, maxLoc;
                // 是否匹配成功
                bool isMatch = false;
                /*
                 * out minVal: 这是一个输出参数，代表图像中的最小值。在模板匹配的情况下，如果你使用的是TM_SQDIFF或TM_SQDIFF_NORMED匹配方法，最小值代表最佳匹配位置。
                    out maxVal: 这也是一个输出参数，代表图像中的最大值。对于其他匹配方法（如TM_CCORR、TM_CCORR_NORMED、TM_CCOEFF、TM_CCOEFF_NORMED），
                    最大值代表最佳匹配位置。
                    out minLoc: 这是一个输出参数，代表最小值的位置（一个Point类型的对象）。如果使用的是平方差匹配方法，这个位置就是模板图像在源图像上的最佳匹配点。
                    out maxLoc: 这是一个输出参数，代表最大值的位置（一个Point类型的对象）。对于相关性和相关系数匹配方法，最大值位置就是模板图像在源图像上的最佳匹配点。
                 */
                matchRes.MinMaxLoc(out minVal, out maxVal, out minLoc, out maxLoc);
                Console.WriteLine($"匹配度: {maxVal:F3},匹配阈值为:{matchThres}");
                // 因为使用的不是平方差匹配法,所以这里使用的是最大值和最大值的位置
                if (maxVal > matchThres)
                {
                    isMatch = true;
                }
                if (isMatch)
                {
                    Point leftTop = maxLoc;
                    Point rightBotom = new Point(leftTop.X + template.Width, leftTop.Y + template.Height);
                    imageColorUsed.Rectangle(leftTop, rightBotom, Scalar.Green, 2);
                    Point midPos = GetTwoPointsMiddle(leftTop, rightBotom);
                    //Console.WriteLine($"找到的圆心坐标为: {midPos}");
                    imageColorUsed.Circle(midPos, 3, Scalar.Red, 2);
                    ImageShow($"CenterFinded_({maxVal}_Index_{templateIndex})", imageColorUsed);
                    resList.Add(new Tuple<double, Point, Size>(maxVal, midPos, template.Size()));
                }

                templateIndex++;
            }

            if (resList.Count == 0)
            {
                opRes.Message = $"找方向标识失败,没有找到符合要求的方向标识,目前的设置的匹配度为{matchThres}";
                return opRes;
            }
            else
            {
                Mat imageColorUsed = imageColor.Clone();
                var resFinal = resList.OrderByDescending(x => x.Item1).First();
                Point midPos = resFinal.Item2;
                Size templateSize = resFinal.Item3;
                Point leftTop = new Point(midPos.X - templateSize.Width / 2, midPos.Y - templateSize.Height / 2);
                Point rightBottom = new Point(midPos.X + templateSize.Width / 2, midPos.Y + templateSize.Height / 2);
                Cv2.DestroyAllWindows();
                imageColorUsed.Rectangle(leftTop, rightBottom, Scalar.Green, 2);
                imageColorUsed.Circle(midPos, 5, Scalar.Red, 3);
                imageColor.Line(new Point(midPos.X - templateSize.Width, midPos.Y), new Point(midPos.X + templateSize.Width, midPos.Y), Scalar.Green, 3);
                imageColor.Line(new Point(midPos.X, midPos.Y - templateSize.Height), new Point(midPos.X, midPos.Y + templateSize.Height), Scalar.Green, 3);
                ImageShow("TargetResFinalFind", imageColorUsed);
                opRes.Data = new Tuple<double, Mat>(resFinal.Item1, imageColorUsed);
                opRes.State = true;
            }
            return opRes;
        }


        /// <summary>
        /// 将图像根据板边的位置矫正成正确的方向,就是让靶标争取是水平方向,在截取靶标和识别的时候都先做这一步.
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        public static Mat RotateImageToRightPos(Mat src)
        {
            ImageShow("Original", src);
            int rows = src.Rows;
            int cols = src.Cols;
            Rect roi = new Rect(cols * 1 / 3, 0, cols * 2 / 3, rows);
            Mat imageUsed = new Mat(src, roi);
            ImageShow("ImageUsed", imageUsed);

            // 噪声去除
            int ksize = 13;
            Mat medianFilter = new Mat();
            Cv2.MedianBlur(imageUsed, medianFilter, ksize);
            ImageShow("MediaBlur", medianFilter);

            Mat thresOut = new Mat();
            // 计算旋转角度
            Cv2.Threshold(medianFilter, thresOut, 100, 255, ThresholdTypes.Binary);
            ImageShow("ImageThresed", thresOut);
            Mat opened = new Mat();
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 5));
            Cv2.MorphologyEx(thresOut, opened, MorphTypes.Close, kernel, iterations: 3);
            ImageShow("Opened", opened);
            return src;
        }

        public static Point GetTwoPointsMiddle(Point pt1, Point pt2)
        {
            return new Point((pt1.X + pt2.X) / 2, (pt1.Y + pt2.Y) / 2);
        }
    }

}